[file name]: PostEdit.vue
[file content begin]
<template>
    <el-dialog v-model="dialogVisible" title="编辑动态" width="500px" :before-close="handleClose" class="post-edit-dialog">
        <div class="post-edit">
            <div class="edit-header">
                <img :src="userStore.getUserAvatar() || '/default-avatar.png'" alt="头像" class="user-avatar" />
                <div class="user-info">
                    <div class="username">{{ userStore.getDisplayName() }}</div>
                    <div class="user-tag">校友</div>
                </div>
            </div>

            <div class="edit-content">
                <el-input v-model="editForm.content" type="textarea" :rows="4" placeholder="分享你的近况..." maxlength="500"
                    show-word-limit resize="none" class="content-input" />

                <!-- 图片预览 -->
                <div v-if="editImages.length > 0" class="image-preview">
                    <div v-for="(image, index) in editImages" :key="index" class="image-item">
                        <img :src="image.url" alt="预览图片" @click="previewImages(index)" />
                        <div class="image-actions">
                            <el-icon @click="removeImage(index)">
                                <Close />
                            </el-icon>
                        </div>
                    </div>
                </div>

                <!-- 操作栏 -->
                <div class="edit-actions">
                    <div class="action-buttons">
                        <el-tooltip content="添加图片" placement="top">
                            <el-button text @click="triggerImageUpload" class="image-upload-btn">
                                <el-icon>
                                    <Picture />
                                </el-icon>
                                添加图片
                            </el-button>
                        </el-tooltip>
                    </div>
                </div>

                <!-- 隐藏的文件输入 -->
                <input ref="fileInput" type="file" multiple accept="image/*" @change="handleImageSelect"
                    style="display: none" />
            </div>
        </div>

        <template #footer>
            <span class="dialog-footer">
                <el-button @click="handleClose" class="cancel-btn">取消</el-button>
                <el-button type="primary" @click="handleSubmit" :loading="submitting"
                    :disabled="!editForm.content.trim()" class="save-btn">
                    <el-icon v-if="!submitting">
                        <Check />
                    </el-icon>
                    {{ submitting ? '保存中...' : '保存更改' }}
                </el-button>
            </span>
        </template>

        <!-- 图片预览对话框 -->
        <el-dialog v-model="imagePreviewVisible" :title="`图片 ${currentImageIndex + 1} / ${editImages.length}`"
            width="80%" top="5vh" align-center class="image-preview-dialog">
            <div class="image-preview-container">
                <img :src="currentImageUrl" alt="预览图片" class="preview-image" />
                <div class="preview-actions">
                    <el-button @click="prevImage" :disabled="currentImageIndex === 0" class="nav-btn">
                        <el-icon>
                            <ArrowLeft />
                        </el-icon>
                        上一张
                    </el-button>
                    <el-button @click="nextImage" :disabled="currentImageIndex === editImages.length - 1"
                        class="nav-btn">
                        下一张
                        <el-icon>
                            <ArrowRight />
                        </el-icon>
                    </el-button>
                    <el-button type="primary" @click="imagePreviewVisible = false" class="close-btn">
                        关闭
                    </el-button>
                </div>
            </div>
        </el-dialog>
    </el-dialog>
</template>
<script setup>
import { ref, watch, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Picture, Close, ArrowLeft, ArrowRight, Check } from '@element-plus/icons-vue'
import { usePostsStore } from '@/stores/posts'
import { useUserStore } from '@/stores/user'

const props = defineProps({
    visible: {
        type: Boolean,
        default: false
    },
    post: {
        type: Object,
        default: null
    }
})

const emit = defineEmits(['update:visible', 'success'])

const postsStore = usePostsStore()
const userStore = useUserStore()

const dialogVisible = ref(false)
const editForm = ref({
    content: '',
    images: []
})
const editImages = ref([])
const submitting = ref(false)
const fileInput = ref(null)

// 图片预览相关
const imagePreviewVisible = ref(false)
const currentImageIndex = ref(0)
const currentImageUrl = computed(() => editImages.value[currentImageIndex.value]?.url || '')

// 图片预览功能
const previewImages = (index) => {
    currentImageIndex.value = index
    imagePreviewVisible.value = true
}

const prevImage = () => {
    if (currentImageIndex.value > 0) {
        currentImageIndex.value--
    }
}

const nextImage = () => {
    if (currentImageIndex.value < editImages.value.length - 1) {
        currentImageIndex.value++
    }
}


watch(() => props.visible, (newVal) => {
    dialogVisible.value = newVal
    if (newVal && props.post) {
        initEditForm()
    }
})

watch(dialogVisible, (newVal) => {
    emit('update:visible', newVal)
})

const initEditForm = () => {
    if (props.post) {
        editForm.value = {
            content: props.post.content || '',
            images: props.post.images || []
        }

        // 初始化图片预览
        editImages.value = []
        if (props.post.images && props.post.images !== 'null') {
            const imagesArray = props.post.images.split(',').filter(img => img.trim())
            editImages.value = imagesArray.map(url => ({
                url: url.trim(),
                file: null,
                uploading: false
            }))
        }
    }
}

const handleClose = () => {
    dialogVisible.value = false
}

const triggerImageUpload = () => {
    fileInput.value.click()
}

const handleImageSelect = async (event) => {
    const files = Array.from(event.target.files)
    if (files.length + editImages.value.length > 9) {
        ElMessage.warning('最多只能上传9张图片')
        return
    }

    for (const file of files) {
        try {
            const url = URL.createObjectURL(file)
            const newImage = {
                url,
                file,
                uploading: true
            }
            editImages.value.push(newImage)

            const response = await postsStore.uploadPostImage(file)
            if (response.code === 0) {
                const imageIndex = editImages.value.findIndex(img => img.url === url)
                if (imageIndex !== -1) {
                    URL.revokeObjectURL(url)
                    editImages.value[imageIndex].url = response.data.url
                    editImages.value[imageIndex].uploading = false
                }
            } else {
                throw new Error(response.msg)
            }
        } catch (error) {
            console.error('图片上传失败:', error)
            ElMessage.error('图片上传失败')
            const failedIndex = editImages.value.findIndex(img => img.uploading)
            if (failedIndex !== -1) {
                const failedImage = editImages.value[failedIndex]
                URL.revokeObjectURL(failedImage.url)
                editImages.value.splice(failedIndex, 1)
            }
        }
    }

    event.target.value = ''
}

const removeImage = (index) => {
    if (index >= 0 && index < editImages.value.length) {
        const image = editImages.value[index]
        if (image.url.startsWith('blob:')) {
            URL.revokeObjectURL(image.url)
        }
        editImages.value.splice(index, 1)
    }
}

const handleSubmit = async () => {
    if (!editForm.value.content.trim()) {
        ElMessage.warning('请输入内容')
        return
    }

    submitting.value = true
    try {
        const updateData = {
            content: editForm.value.content.trim()
        }

        // 处理图片 - 保持为数组格式
        if (editImages.value.length > 0) {
            // 提取所有图片的URL并过滤空值，保持为数组格式
            const imageUrls = editImages.value.map(img => img.url).filter(url => url && url.trim())
            if (imageUrls.length > 0) {
                // 保持为数组格式
                updateData.images = imageUrls
            }
        } else {
            // 如果没有图片，设置为空数组
            updateData.images = []
        }

        await postsStore.updatePostItem(props.post.post_id, updateData)
        dialogVisible.value = false
        emit('success')
    } catch (error) {
        console.error('更新失败:', error)
        ElMessage.error('更新失败：' + error.message)
    } finally {
        submitting.value = false
    }
}
</script>

<style scoped>
.post-edit {
    padding: 0;
}

.edit-header {
    display: flex;
    align-items: flex-start;
    margin-bottom: 20px;
}

.user-avatar {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    margin-right: 16px;
    border: 3px solid #e4e7ed;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

.user-info {
    flex: 1;
}

.username {
    font-weight: 600;
    margin-bottom: 4px;
    color: #2c3e50;
    font-size: 16px;
}

.user-tag {
    font-size: 12px;
    color: #667eea;
    background: rgba(102, 126, 234, 0.1);
    padding: 2px 8px;
    border-radius: 10px;
    display: inline-block;
}

.content-input {
    margin-bottom: 20px;
}

:deep(.content-input .el-textarea__inner) {
    border: 2px solid #e4e7ed;
    border-radius: 12px;
    padding: 16px;
    font-size: 14px;
    line-height: 1.6;
    transition: all 0.3s ease;
    background: rgba(255, 255, 255, 0.8);
}

:deep(.content-input .el-textarea__inner:focus) {
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
    background: white;
}

.image-preview {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
    gap: 12px;
    margin-bottom: 20px;
}

.image-item {
    position: relative;
    aspect-ratio: 1;
    border-radius: 12px;
    overflow: hidden;
    background: #f5f7fa;
    border: 2px solid transparent;
    transition: all 0.3s ease;
}

.image-item:hover {
    border-color: #667eea;
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.2);
}

.image-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    cursor: pointer;
    transition: transform 0.3s ease;
}

.image-item img:hover {
    transform: scale(1.1);
}

.image-actions {
    position: absolute;
    top: 4px;
    right: 4px;
    background: rgba(255, 255, 255, 0.9);
    border-radius: 50%;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: #ff6b6b;
    font-size: 12px;
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.8);
    transition: all 0.3s ease;
}

.image-actions:hover {
    background: #ff6b6b;
    color: white;
    transform: scale(1.1);
}

.edit-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-top: 16px;
    border-top: 1px solid rgba(228, 231, 237, 0.6);
}

.action-buttons {
    display: flex;
    gap: 8px;
}

.image-upload-btn {
    color: #667eea;
    border: 1px dashed #667eea;
    border-radius: 10px;
    padding: 8px 16px;
    transition: all 0.3s ease;
}

.image-upload-btn:hover {
    background: rgba(102, 126, 234, 0.1);
    transform: translateY(-1px);
}

:deep(.action-buttons .el-button) {
    padding: 8px 16px;
}

.dialog-footer {
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    width: 100%;
}

.cancel-btn {
    border-radius: 10px;
    padding: 10px 20px;
    border: 1px solid #dcdfe6;
}

.save-btn {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    border-radius: 10px;
    padding: 10px 24px;
    font-weight: 500;
    transition: all 0.3s ease;
}

.save-btn:hover:not(:disabled) {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
}

/* 图片预览样式 */
.image-preview-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
}

.preview-image {
    max-width: 100%;
    max-height: 70vh;
    object-fit: contain;
    border-radius: 16px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
}

.preview-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
}

.nav-btn {
    border-radius: 10px;
    padding: 8px 16px;
}

.close-btn {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    border: none;
    border-radius: 10px;
    padding: 8px 20px;
}

:deep(.post-edit-dialog .el-dialog) {
    border-radius: 20px;
    overflow: hidden;
}

:deep(.post-edit-dialog .el-dialog__header) {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    margin: 0;
    padding: 20px 24px;
}

:deep(.post-edit-dialog .el-dialog__title) {
    color: white;
    font-weight: 600;
    font-size: 18px;
}

:deep(.post-edit-dialog .el-dialog__body) {
    padding: 24px;
}

:deep(.post-edit-dialog .el-dialog__footer) {
    padding: 16px 24px 24px;
    border-top: 1px solid rgba(228, 231, 237, 0.6);
}
</style>
